package com.adu.music.util;

import com.adu.music.bean.Proxy;
import com.adu.music.proxy.ProxyPool;
import com.adu.music.proxy.ProxyPoolFactory;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * @author duchuanchuan
 * @date 2016/12/4
 */
public class JsoupUtils {

    private static final Logger logger = LoggerFactory.getLogger(JsoupUtils.class);
    private static final Map<String, String> baseHeaders = new HashMap<>();
    private static final Map<String, String> additionalHeaders = new HashMap<>();
    private static final String userAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36";
    private static final String charset = "UTF-8";
    // 超时时间30秒
    private static final int timeout = 30000;
    private static final int RETRY_MAX_NUM = 6;

    private static ProxyPool proxyPool;
    private static boolean useProxy;
    private static ThreadLocal<Proxy> proxyLocal;

    static {
        additionalHeaders.put("Referer", "http://music.163.com/");
        additionalHeaders.put("Origin", "http://music.163.com");
        additionalHeaders.put("Host", "music.163.com");
        baseHeaders.put("Connection", "keep-alive");
        baseHeaders.put("Accept-Language", "zh-CN,zh;q=0.8");
        useProxy = ConfigManager.getUseProxy();
        if (useProxy) {
            proxyPool = ProxyPoolFactory.createUnBlockedProxyPool();
            proxyLocal = new ThreadLocal<>();
        }
    }

    public static Connection connectWithBaseHeaders(String url) {
        return Jsoup.connect(url).userAgent(userAgent)
                .headers(baseHeaders).postDataCharset(charset)
                .timeout(timeout);
    }

    private static Connection connect(String url) {
        return Jsoup.connect(url).userAgent(userAgent)
                .headers(baseHeaders).headers(additionalHeaders)
                .postDataCharset(charset).timeout(timeout);
    }

    public static Optional<Document> get(String url) {
        Optional<Document> doc = Optional.empty();
        for (int i = 0; i < RETRY_MAX_NUM; i++) {
            Proxy proxy = null;
            Connection conn = connect(url);
            if (useProxy) {
                proxy = getProxy();
                logger.info("set proxy {}:{}", proxy.getIp(), proxy.getPort());
                conn.proxy(proxy.getIp(), proxy.getPort());
            }
            double spend = 0;
            try {
                long start = System.currentTimeMillis();
                doc = Optional.of(conn.get());
                long end = System.currentTimeMillis();
                spend = (end - start) / 1000;
                break;
            } catch (IOException e) {
                if (proxy != null && e instanceof SocketTimeoutException) {
                    logger.error("get visit URl:proxy.setUnavailable();{} SocketTimeoutException: {}", url, e);
                }
            } finally {
                if (proxy != null) {
                    proxy.setSpeed(spend);

                    release(proxy);
                }
            }
        }
        return doc;
    }

    public static Optional<Document> post(String url, Map<String, String> params) {
        Optional<Document> doc = Optional.empty();
        for (int i = 0; i < RETRY_MAX_NUM; i++) {
            Connection conn = connect(url);
            Proxy proxy = null;
            if (useProxy) {
                proxy = getProxy();
                logger.info("set proxy {}:{}", proxy.getIp(), proxy.getPort());
                conn.proxy(proxy.getIp(), proxy.getPort());
            }
            double spend = 0;
            try {
                long start = System.currentTimeMillis();
                doc = Optional.of(conn.data(params).post());
                long end = System.currentTimeMillis();
                spend = (end - start) / 1000;
                break;
            } catch (IOException e) {
                if (proxy != null && e instanceof SocketTimeoutException) {
                    proxy.setUnavailable();
                    logger.error("post visit URL:{} SocketTimeoutException: {}", url, e);
                }
            } finally {
                if (proxy != null) {
                    proxy.setSpeed(spend);
                    release(proxy);
                }
            }
        }
        return doc;
    }

    public static Optional<Document> post(String url, Map<String, String> params, Map<String, String> headers) {
        Optional<Document> doc = Optional.empty();
        for (int i = 0; i < RETRY_MAX_NUM; i++) {
            Proxy proxy = null;
            Connection conn = connect(url);
            if (useProxy) {
                proxy = getProxy();
                logger.info("set proxy {}:{}", proxy.getIp(), proxy.getPort());
                conn.proxy(proxy.getIp(), proxy.getPort());
            }
            double spend = 0;
            try {
                if (!headers.isEmpty()) {
                    for (Map.Entry<String, String> entry : headers.entrySet()) {
                        conn.header(entry.getKey(), entry.getValue());
                    }
                }
                long start = System.currentTimeMillis();
                doc = Optional.of(conn.data(params).post());
                long end = System.currentTimeMillis();
                spend = (end - start) / 1000;
                break;
            } catch (IOException e) {
                if (proxy != null && e instanceof SocketTimeoutException) {
                    proxy.setUnavailable();
                    logger.error("visit URl:{} SocketTimeoutException: {}, method:post", url, e);
                }
            } finally {
                if (proxy != null) {
                    proxy.setSpeed(spend);
                    release(proxy);
                }
            }
        }
        return doc;
    }

    private static Proxy getProxy(){
        Proxy proxy = proxyLocal.get();
        if(proxy == null){
            proxy = proxyPool.getProxy();
            proxyLocal.set(proxy);
        }
        return proxy;
    }

    private static void release(Proxy proxy){
        if(!proxy.isAvailable()){
            logger.info("release proxy : {} , is available : {}", proxy.getIp(), proxy.isAvailable());
            proxyLocal.remove();
            proxyPool.release(proxy);
        }
    }

    public static void releaseResources() {
        if(proxyPool != null)
            proxyPool.shutdown();
    }

    public static void openProxyPool(){
        if(proxyPool != null)
            proxyPool.open();
    }

}
